package com.ruben.simplescaffold.manager.impl;

import cn.hutool.core.lang.Opt;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.ruben.simplescaffold.exception.ServerException;
import com.ruben.simplescaffold.manager.RedisManager;
import com.ruben.simplescaffold.utils.ISupplier;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.security.SecureRandom;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * redis manager层
 *
 * @author <achao1441470436@gmail.com>
 * @since 2021/5/9 0009 17:49
 */
@Service
public class RedisManagerImpl implements RedisManager {
    /**
     * 随机数
     */
    public static SecureRandom SECURE_RANDOM = new SecureRandom();
    /**
     * redis缓存时间
     */
    public static int EXPIRE_SECONDS = 5 * 60;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * redis缓存处理查询内容，处理缓存雪崩
     *
     * @param key           redis中的key
     * @param mysqlSupplier 查询数据库操作
     * @param typeReference 指定类型
     * @return T
     * @author <achao1441470436@gmail.com>
     * @since 2021/5/9 0009 20:33
     */
    @Override
    @Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED, isolation = Isolation.READ_UNCOMMITTED)
    public <T> T getFromRedisOrPutIntoMysql(String key, ISupplier<T> mysqlSupplier, TypeReference<T> typeReference) {
        // 判断传入key是否为空
        Opt.ofNullable(key).filter(StringUtils::isNotEmpty)
                .orElseThrow(() -> new ServerException("key不能为空"));
        // 如果redis中存在，直接返回
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        if (Objects.nonNull(ops.get(key))) {
            return JSON.parseObject(ops.get(key), typeReference);
        }
        // 查数据库并放入缓存这个操作加锁【缓存击穿】
        synchronized (this) {
            // 否则从数据库中取出
            return Opt.ofNullable(mysqlSupplier).map(Supplier::get)
                    // 如果有值则放入缓存，随机时间【缓存雪崩】
                    .peek(v -> ops.set(key,
                            JSON.toJSONString(v),
                            SECURE_RANDOM.nextInt(EXPIRE_SECONDS),
                            TimeUnit.SECONDS))
                    // 没值则放入空对象"{}"【缓存穿透】并返回null
                    .orElseGet(() -> {
                        ops.set(key, "{}", SECURE_RANDOM.nextInt(EXPIRE_SECONDS), TimeUnit.SECONDS);
                        return null;
                    });
        }
    }

}
